#include <Windows.h>
#include <WindowsX.h>

#include <ctime>

// for console
#include <iostream>
#include <io.h>
#include <fcntl.h>

#include "Structures.h"
#include "interfaces.h"
#include "PFile.h"

// Managers
#include "MemoryManager.h"
#include "PScheduler.h"
#include "GraphicsManager.h"
#include "PlatformManager.h"

// Modules
#include "Renderer.h"

using namespace Proton;
using namespace Proton::Managers;
using namespace Proton::Modules;

using namespace std;

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, 
		WPARAM wParam, LPARAM lParam);

void CreateConsoleLog();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
	LPSTR lpCmdLine, int nCmdShow)
{
	//CreateConsoleLog();
	//cout << "console created" << endl;

	GRAPHICS_SETTINGS_DESC gsd;
	gsd.fullscreen = false;
	gsd.msCount = 4;
	gsd.windowWidth = 800;
	gsd.windowHeight = 600;

	// create managers
	
	/*For now allocate 32MB, 
	Later let asset pipeline give hint about memory usage*/
	MemoryManager* pMemoryManager = new MemoryManager(32, 1024);
	PScheduler* pScheduler = new PScheduler();
	GraphicsManager* pGraphics = new GraphicsManager(&gsd);
	PlatformManager* pPlatform = new PlatformManager();

	pScheduler->initialize(pPlatform, pGraphics);
	pPlatform->initialize(hInstance, lpCmdLine, nCmdShow, pScheduler, pGraphics);
	pGraphics->initialize(pPlatform, pScheduler);
	
	pPlatform->log("initialization of managers ok");
	// give modules access to managers
	PModule::pGraphics = pGraphics;
	PModule::pScheduler = pScheduler;
	PModule::pPlatform = pPlatform;

	pPlatform->log("access of managers to modules ok");

	// create modules
	Renderer* pRenderer = new Renderer();

	pPlatform->log("renderer created ok");
		
	// initialize modules and load their content
	pScheduler->initializeModules();
	pScheduler->loadContentOfModules();

	pPlatform->log("modules initialized & loaded ok");

	// test memory allocation
	char memStat[100];
	memset(memStat, 0, 40);
	sprintf_s(memStat, "Total memory: %f MB", (float)pMemoryManager->getTotalMemory()/1048576);
	pPlatform->log(memStat);
	sprintf_s(memStat, "Used memory: %f MB", (float)pMemoryManager->getUsedMemory()/1048576);
	pPlatform->log(memStat);
	pPlatform->log("Allocationg memory (...)");

	void* block4MB = pMemoryManager->allocate(4194304);
	void* block2MB = pMemoryManager->allocate(2097152);
	void* block1MB = pMemoryManager->allocate(1048576);
	void* block512KB = pMemoryManager->allocate(524288);
	void* block256KB = pMemoryManager->allocate(262144);
	void* block192KB = pMemoryManager->allocate(196608);
	void* block48KB  = pMemoryManager->allocate(49152);
	void* block32KB  = pMemoryManager->allocate(32768);

	void* block10MB = pMemoryManager->allocate(10485760);
	void* block10MB_2 = pMemoryManager->allocate(10485760);
	void* block10MB_3 = pMemoryManager->allocate(10485760);
	// this one should create another heap of size approx. 31MB
	void* block10MB_4 = pMemoryManager->allocate(10485760);

	sprintf_s(memStat, "Total memory: %f MB", (float)pMemoryManager->getTotalMemory()/1048576);
	pPlatform->log(memStat);
	sprintf_s(memStat, "Used memory: %f MB", (float)pMemoryManager->getUsedMemory()/1048576);
	pPlatform->log(memStat);

	// measure performance of standard allocator
	sprintf_s(memStat, "Mesuring performance of standard allocator: ");
	pPlatform->log(memStat);

	unsigned long commence, runtime;
	timeBeginPeriod(1);
	commence = timeGetTime();
	void* pPtrs[30000];
	for (int i = 0; i < 30000; i++)
	{
		pPtrs[i] = new U8[256*(i%18)];
	}
	runtime = timeGetTime() - commence;
	timeEndPeriod(1);
	for (int i = 0; i < 30000; i++)
	{
		delete[] pPtrs[i];
	}

	sprintf_s(memStat, "%lu ms", runtime);
	pPlatform->log(memStat);

	// measure MemoryManager allocator
	sprintf_s(memStat, "Mesuring performance of MemoryManager allocator: ");
	pPlatform->log(memStat);
	timeBeginPeriod(1);
	commence = timeGetTime();
	for (int i = 0; i < 30000; i++)
	{
		pPtrs[i] = pMemoryManager->allocate(256*(i%18));
	}
	runtime = timeGetTime() - commence;
	timeEndPeriod(1);

	sprintf_s(memStat, "%lu ms", runtime);
	pPlatform->log(memStat);
	timeEndPeriod(1);

	sprintf_s(memStat, "Total memory: %f MB", (float)pMemoryManager->getTotalMemory()/1048576);
	pPlatform->log(memStat);
	sprintf_s(memStat, "Used memory: %f MB", (float)pMemoryManager->getUsedMemory()/1048576);
	pPlatform->log(memStat);

	// test file read
	PFile* pInputFile = new PFile("data/inputTestFile.TXT");
	if (pPlatform->syncReadFile(pInputFile))
		pPlatform->log("file read test - no error detected");
	else
		pPlatform->log("file read test failed");

	// test file write
	PFile* pOutputFile = new PFile("data/outputTestFile.TXT", pInputFile->getDataPtr(), false, pInputFile->getDataSize());
	if (pPlatform->syncWriteFile(pOutputFile))
		pPlatform->log("file write test - no error detected");
	else
		pPlatform->log("file write test failed");

	delete pInputFile;
	delete pOutputFile;

	// enter the loop
	pPlatform->log("entering the loop");
	pPlatform->log("...");
	WPARAM param = pScheduler->loop();

	pPlatform->log("loop exited");

	// to do: let PlatformManager handle the MM
	pPlatform->releaseResources();
	pPlatform->log("resources released ok");
	pPlatform->log("exit");
	delete pGraphics;
	delete pScheduler;
	delete pPlatform;
	delete pMemoryManager;
	
	delete pRenderer;

	return param;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, 
	WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		} break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

void CreateConsoleLog()
{	
	AllocConsole();	
	SetConsoleTitle(L"Proton Console");	

	int hConHandle;	
	long lStdHandle;	
	FILE *fp;	
	
	// redirect unbuffered STDOUT to the console	
	lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);	
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);	
	fp = _fdopen( hConHandle, "w" );	
	*stdout = *fp;	
	setvbuf( stdout, NULL, _IONBF, 0 );	
	
	// redirect unbuffered STDIN to the console	
	lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);	
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);	
	fp = _fdopen( hConHandle, "r" );	
	*stdin = *fp;	setvbuf( stdin, NULL, _IONBF, 0 );	
	
	// redirect unbuffered STDERR to the console	
	lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);	
	hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);	
	fp = _fdopen( hConHandle, "w" );	
	*stderr = *fp;	setvbuf( stderr, NULL, _IONBF, 0 );
}